#include <AVUIDeferredReference.h>

namespace AVUI {

/// <summary>Provides data for various property changed events. Typically these events report effective value changes in the value of a read-only dependency property. Another usage is as part of a <see cref="T:System.Windows.PropertyChangedCallback" /> implementation.</summary>
class DependencyPropertyChangedEventArgs : public EventArgs
{

public:
    DECLARE_ELEMENT(DependencyPropertyChangedEventArgs, EventArgs);

#pragma region Public Methods

    /// <summary>Initializes a new instance of the <see cref="T:System.Windows.DependencyPropertyChangedEventArgs" /> class.</summary>
    /// <param name="property">The identifier for the dependency property that changed.</param>
    /// <param name="oldValue">The value of the property before the change reported by the relevant event or state change.</param>
    /// <param name="newValue">The value of the property after the change reported by the relevant event or state change.</param>
    DependencyPropertyChangedEventArgs(DependencyProperty* pProperty, Object* pOldValue, Object* pNewValue)
    {
        m_pProperty = pProperty;
        m_pMetadata = NULL;
        m_oldEntry = EffectiveValueEntry(pProperty);
        m_newEntry = m_oldEntry;
        m_oldEntry.set_Value(pOldValue);
        m_newEntry.set_Value(pNewValue);
        m_flags = 0;
        m_operationType = OperationType::Unknown;
        set_IsAValueChange(true);
    }

#pragma endregion Public Methods

#pragma region Public Properties

    /// <summary>Gets the value of the property after the change.</summary>
    /// <returns>The property value after the change.</returns>
    TRefCountedPtr<Object> get_NewValue()
    {
        EffectiveValueEntry flattenedEntry = get_NewEntry().GetFlattenedEntry(RequestFlags::FullyResolved);
        if (flattenedEntry.get_IsDeferredReference())
        {
            flattenedEntry.set_Value( ((DeferredReference*) (flattenedEntry.get_Value()))->GetValue(flattenedEntry.get_BaseValueSource()));
            flattenedEntry.set_IsDeferredReference(false);
        }
        return flattenedEntry.get_Value();
    }

    /// <summary>Gets the value of the property before the change.</summary>
    /// <returns>The property value before the change.</returns>
    TRefCountedPtr<Object> get_OldValue()
    {
        EffectiveValueEntry flattenedEntry = get_OldEntry().GetFlattenedEntry(RequestFlags::FullyResolved);
        if (flattenedEntry.get_IsDeferredReference())
        {
            flattenedEntry.set_Value( ((DeferredReference*) (flattenedEntry.get_Value()))->GetValue(flattenedEntry.get_BaseValueSource()));
            flattenedEntry.set_IsDeferredReference(false);
        }
        return flattenedEntry.get_Value();
    }


    /// <summary>Gets the identifier for the dependency property where the value change occurred.</summary>
    /// <returns>The identifier field of the dependency property where the value change occurred.</returns>
    DependencyProperty* get_Property() { return m_pProperty; }
 
#pragma endregion Public Properties

private: 

#pragma region Private Flags

class PrivateFlags
{
public:
    enum Enum
    {
        IsASubPropertyChange = 2,
        IsAValueChange = 1
    };
};

#pragma endregion Private Flags

public:

#pragma region Internal Methods

    DependencyPropertyChangedEventArgs(DependencyProperty* pProperty, PropertyMetadata* pMetadata, Object* pValue)
    {
        m_pProperty = pProperty;
        m_pMetadata = pMetadata;
        m_oldEntry = EffectiveValueEntry(pProperty);
        m_oldEntry.set_Value(pValue);
        m_newEntry = m_oldEntry;
        m_flags = 0;
        m_operationType = OperationType::Unknown;
        set_IsASubPropertyChange(true);
    }

    DependencyPropertyChangedEventArgs(DependencyProperty* pProperty, PropertyMetadata* pMetadata, Object* pOldValue, Object* pNewValue)
    {
        m_pProperty = pProperty;
        m_pMetadata = pMetadata;
        m_oldEntry = EffectiveValueEntry(pProperty);
        m_newEntry = m_oldEntry;
        m_oldEntry.set_Value(pOldValue);
        m_newEntry.set_Value(pNewValue);
        m_flags = 0;
        m_operationType = OperationType::Unknown;
        set_IsAValueChange(true);
    }

    DependencyPropertyChangedEventArgs(DependencyProperty* pProperty, PropertyMetadata* pMetadata, bool isAValueChange, const EffectiveValueEntry& oldEntry, const EffectiveValueEntry& newEntry, OperationType::Enum operationType)
    {
        m_pProperty = pProperty;
        m_pMetadata = pMetadata;
        m_oldEntry = oldEntry;
        m_newEntry = newEntry;
        m_flags = 0;
        m_operationType = operationType;
        set_IsAValueChange(isAValueChange);
    }

#pragma endregion Internal Methods

#pragma region Internal Properties

    bool get_IsASubPropertyChange() { return ReadPrivateFlag(PrivateFlags::IsASubPropertyChange); }
    void set_IsASubPropertyChange(bool value) { WritePrivateFlag(PrivateFlags::IsASubPropertyChange, value); }

    bool get_IsAValueChange() { return ReadPrivateFlag(PrivateFlags::IsAValueChange); }
    void set_IsAValueChange(bool value) { WritePrivateFlag(PrivateFlags::IsAValueChange, value); }

    bool get_IsNewValueDeferred() { return m_newEntry.get_IsDeferredReference(); }
    bool get_IsNewValueModified() { return m_newEntry.get_HasModifiers(); }

    bool get_IsOldValueDeferred() { return m_oldEntry.get_IsDeferredReference(); }
    bool get_IsOldValueModified() { return m_oldEntry.get_HasModifiers(); }

    PropertyMetadata* get_Metadata() { return m_pMetadata; }

    EffectiveValueEntry get_NewEntry() { return m_newEntry; }
    BaseValueSource::Enum get_NewValueSource() { return m_newEntry.get_BaseValueSource(); }

    EffectiveValueEntry get_OldEntry() { return m_oldEntry; }
    BaseValueSource::Enum get_OldValueSource() { return m_oldEntry.get_BaseValueSource(); }

    OperationType::Enum get_OperationType() { return m_operationType; }

#pragma endregion Internal Properties

private:

#pragma region Private Methods

    bool ReadPrivateFlag(PrivateFlags::Enum bit)
    {
        return (m_flags & bit) != 0;
    }

    void WritePrivateFlag(PrivateFlags::Enum bit, bool value)
    {
        if (value)
        {
            m_flags = m_flags | bit;
        }
        else
        {
            m_flags = m_flags & (~bit);
        }
    }
#pragma endregion Private Methods

#pragma region Private Fields

    unsigned int m_flags;
    TRefCountedPtr<PropertyMetadata> m_pMetadata;
    EffectiveValueEntry m_newEntry;
    EffectiveValueEntry m_oldEntry;
    OperationType::Enum m_operationType;
    DependencyProperty* m_pProperty;

#pragma endregion Private Fields

};

DEFINE_EVENTHANDLER(DependencyPropertyChanged);

};
